use crate::nested_map::Error::AlreadyExists;
use share::core::channel::Channel;
use share::core::native_version::NativeVersion;
use snafu::OptionExt;
use std::collections::HashMap;
use std::collections::hash_map::Entry;
use std::sync::Arc;
use tokio::sync::RwLock;

#[derive(Debug, snafu::Snafu)]
pub enum Error {
    #[snafu(display("Failed to get inner data. channel: {channel:?}"))]
    ChannelNotFound { channel: Channel },

    #[snafu(display(
        "Failed to get inner data. channel: {channel:?}, native version: {native_version}"
    ))]
    NativeVersionNotFound {
        channel: Channel,
        native_version: NativeVersion,
    },

    #[snafu(display(
        "Failed to data already exist, channel: {channel:?}, native version: {native_version}"
    ))]
    AlreadyExists {
        channel: Channel,
        native_version: NativeVersion,
    },
}

type Result<T> = std::result::Result<T, Error>;

pub trait NestedMap<T> {
    async fn insert(
        &self,
        channel: &Channel,
        native_version: &NativeVersion,
        value: T,
    ) -> Result<()>;
    async fn delete(&self, channel: &Channel, native_version: &NativeVersion) -> Result<()>;
    async fn delete_by_channel(&self, channel: &Channel) -> Result<()>;
    async fn update(
        &self,
        channel: &Channel,
        native_version: &NativeVersion,
        modifier: impl FnOnce(&mut T),
    ) -> Result<()>;

    async fn get_by_channel(
        &self,
        channel: &Channel,
    ) -> Result<Arc<RwLock<HashMap<NativeVersion, T>>>>;
    async fn get_by_channel_and_native_version(
        &self,
        channel: &Channel,
        native_version: &NativeVersion,
    ) -> Result<T>;
}

impl<T> NestedMap<T> for Arc<RwLock<HashMap<Channel, Arc<RwLock<HashMap<NativeVersion, T>>>>>>
where
    T: Clone,
{
    async fn insert(
        &self,
        channel: &Channel,
        native_version: &NativeVersion,
        value: T,
    ) -> Result<()> {
        let settings = {
            let lock = self.read().await;
            lock.get(channel).cloned().context(ChannelNotFoundSnafu {
                channel: channel.clone(),
            })?
        };

        let mut lock = settings.write().await;
        match lock.entry(native_version.clone()) {
            Entry::Occupied(_) => Err(AlreadyExists {
                channel: channel.clone(),
                native_version: native_version.clone(),
            }),
            Entry::Vacant(e) => {
                e.insert(value);
                Ok(())
            }
        }
    }

    async fn delete(&self, channel: &Channel, native_version: &NativeVersion) -> Result<()> {
        let settings = {
            let lock = self.read().await;
            lock.get(channel).cloned().context(ChannelNotFoundSnafu {
                channel: channel.clone(),
            })?
        };

        let mut lock = settings.write().await;
        lock.remove(native_version);

        Ok(())
    }

    async fn delete_by_channel(&self, channel: &Channel) -> Result<()> {
        let mut lock = self.write().await;
        lock.remove(channel).context(ChannelNotFoundSnafu {
            channel: channel.clone(),
        })?;

        Ok(())
    }

    async fn update(
        &self,
        channel: &Channel,
        native_version: &NativeVersion,
        modifier: impl FnOnce(&mut T),
    ) -> Result<()> {
        let settings = {
            let lock = self.read().await;
            lock.get(channel).cloned().context(ChannelNotFoundSnafu {
                channel: channel.clone(),
            })?
        };

        let mut lock = settings.write().await;
        let setting = lock
            .get_mut(native_version)
            .context(NativeVersionNotFoundSnafu {
                channel: channel.clone(),
                native_version: native_version.clone(),
            })?;

        modifier(setting);

        Ok(())
    }

    async fn get_by_channel(
        &self,
        channel: &Channel,
    ) -> Result<Arc<RwLock<HashMap<NativeVersion, T>>>> {
        self.read()
            .await
            .get(channel)
            .cloned()
            .context(ChannelNotFoundSnafu {
                channel: channel.clone(),
            })
    }

    async fn get_by_channel_and_native_version(
        &self,
        channel: &Channel,
        native_version: &NativeVersion,
    ) -> Result<T> {
        let settings = {
            let lock = self.read().await;
            lock.get(channel).cloned().context(ChannelNotFoundSnafu {
                channel: channel.clone(),
            })?
        };

        let lock = settings.read().await;
        let setting = lock
            .get(native_version)
            .context(NativeVersionNotFoundSnafu {
                channel: channel.clone(),
                native_version: native_version.clone(),
            })?
            .clone();

        Ok(setting)
    }
}
